﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Dns.cs" company="Rolosoft Ltd">
//   © Rolosoft Ltd
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

#region License

// // Copyright 2013 Rolosoft Ltd
// // Licensed under the Apache License, Version 2.0 (the "License");
// // you may not use this file except in compliance with the License.
// // You may obtain a copy of the License at
// // http://www.apache.org/licenses/LICENSE-2.0
// // Unless required by applicable law or agreed to in writing, software
// // distributed under the License is distributed on an "AS IS" BASIS,
// // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// // See the License for the specific language governing permissions and
// // limitations under the License.
#endregion

namespace Rsft.Net.Dns
{
    #region Usings

    using System;
    using System.Diagnostics.Contracts;
    using System.Net;
    using System.Security.Permissions;

    using Rsft.Net.Dns.Entities;
    using Rsft.Net.Dns.Interfaces;

    using Wintellect.Threading.AsyncProgModel;
    using Wintellect.Threading.ResourceLocks;

    #endregion

    /// <summary>
    /// The host entry resolve method type.
    /// </summary>
    internal enum HostEntryResolveMethodType
    {
        /// <summary>
        /// The none.
        /// </summary>
        None, 

        /// <summary>
        /// The by host name or address.
        /// </summary>
        ByHostNameOrAddress, 

        /// <summary>
        /// The by address.
        /// </summary>
        ByAddress
    }

    /// <summary>
    ///     Provides advanced domain name resolution functionality.
    /// </summary>
    /// <remarks>
    ///     Direct replacement for <see cref="System.Net.Dns" />. Supplements existing methods with advanced DNS resolutions for DNS records per RFC1034, RFC1035, RFC1886, RFC2915.
    /// </remarks>
    public static class Dns
    {
        #region Static Fields

        /// <summary>
        /// The APM wrap begin get host addresses.
        /// </summary>
        private static readonly ApmWrap<IResolver> ApmWrapBeginGetHostAddresses = new ApmWrap<IResolver>();

        /// <summary>
        /// The APM wrap begin get host by name.
        /// </summary>
        private static readonly ApmWrap<IResolver> ApmWrapBeginGetHostByName = new ApmWrap<IResolver>();

        /// <summary>
        /// The APM wrap begin get host entry by address.
        /// </summary>
        private static readonly ApmWrap<IResolver> ApmWrapBeginGetHostEntryByAddress = new ApmWrap<IResolver>();

        /// <summary>
        /// The APM wrap begin get host entry by host name or address.
        /// </summary>
        private static readonly ApmWrap<IResolver> ApmWrapBeginGetHostEntryByHostNameOrAddress =
            new ApmWrap<IResolver>();

        /// <summary>
        /// The APM wrap begin query.
        /// </summary>
        private static readonly ApmWrap<IResolver> ApmWrapBeginQuery = new ApmWrap<IResolver>();

        /// <summary>
        /// The APM wrap begin resolve.
        /// </summary>
        private static readonly ApmWrap<IResolver> ApmWrapBeginResolve = new ApmWrap<IResolver>();
        
        /// <summary>
        /// The one many resource lock host entry method type.
        /// </summary>
        private static readonly OneManyResourceLock OneManyResourceLockHostEntryMethodType = new OneManyResourceLock();

        /// <summary>
        /// The one many resource lock configuration
        /// </summary>
        private static readonly OneManyResourceLock OneManyResourceLockConfiguration = new OneManyResourceLock();

        /// <summary>
        ///     The resolver factory
        /// </summary>
        private static readonly IResolverFactory ResolverFactory = new ResolverFactoryDefault();

        /// <summary>
        /// The configuration factory
        /// </summary>
        private static readonly IConfigurationFactory ConfigurationFactory = new ConfigurationFactoryDefault();

        /// <summary>
        /// The host entry resolve method type.
        /// </summary>
        private static HostEntryResolveMethodType hostEntryResolveMethodType;

        /// <summary>
        /// The configuration
        /// </summary>
        private static IConfiguration configuration = ConfigurationFactory.Create();
        
        #endregion
        
        #region Public Events

        /// <summary>
        /// Occurs when [verbose event].
        /// </summary>
        public static event EventHandler<VerboseEventArgs> VerboseEvent;

        #endregion

        #region Public properties

        /// <summary>
        /// Gets or sets the DNS configuration.
        /// </summary>
        /// <value>
        /// The configuration.
        /// </value>
        public static IConfiguration DnsConfiguration
        {
            get
            {
                OneManyResourceLockConfiguration.Enter(false);
                var rtn = configuration;
                OneManyResourceLockConfiguration.Leave();
                return rtn;
            }

            set
            {
                OneManyResourceLockConfiguration.Enter(true);
                configuration = value;
                OneManyResourceLockConfiguration.Leave();
            }
        }

        #endregion

        #region Properties

        /// <summary>
        ///     Gets or sets the type of the host entry resolve method.
        /// </summary>
        /// <value>
        ///     The type of the host entry resolve method.
        /// </value>
        /// <remarks>
        ///     Thread safe
        /// </remarks>
        private static HostEntryResolveMethodType HostEntryResolveMethodType
        {
            get
            {
                OneManyResourceLockHostEntryMethodType.Enter(false);

                var entryResolveMethodType = hostEntryResolveMethodType;

                OneManyResourceLockHostEntryMethodType.Leave();

                return entryResolveMethodType;
            }

            set
            {
                OneManyResourceLockHostEntryMethodType.Enter(true);

                hostEntryResolveMethodType = value;

                OneManyResourceLockHostEntryMethodType.Leave();
            }
        }

        #endregion

        #region Public Methods and Operators

        /// <summary>
        /// Asynchronously returns the Internet Protocol (IP) addresses for the specified host.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.IAsyncResult"/> instance that references the asynchronous request.
        /// </returns>
        /// <param name="hostNameOrAddress">
        /// The host name or IP address to resolve.
        /// </param>
        /// <param name="requestCallback">
        /// An <see cref="T:System.AsyncCallback"/> delegate that references the method to invoke when the operation is complete.
        /// </param>
        /// <param name="state">
        /// A user-defined object that contains information about the operation. This object is passed to the
        ///     <paramref name="requestCallback"/>
        ///     delegate when the operation is complete.
        /// </param>
        /// <exception cref="T:System.ArgumentNullException">
        /// <paramref name="hostNameOrAddress"/> is null.
        /// </exception>
        /// <exception cref="T:System.ArgumentOutOfRangeException">
        /// The length of <paramref name="hostNameOrAddress"/> is greater than 126 characters.
        /// </exception>
        /// <exception cref="T:System.Net.Sockets.SocketException">
        /// An error is encountered when resolving <paramref name="hostNameOrAddress"/>.
        /// </exception>
        /// <exception cref="T:System.ArgumentException">
        /// <paramref name="hostNameOrAddress"/> is an invalid IP address.
        /// </exception>
        [HostProtection(SecurityAction.LinkDemand, ExternalThreading = true)]
        public static IAsyncResult BeginGetHostAddresses(
            string hostNameOrAddress, AsyncCallback requestCallback, object state)
        {
            if (requestCallback == null)
            {
                throw new ArgumentNullException("requestCallback");
            }

            if (string.IsNullOrEmpty(hostNameOrAddress))
            {
                throw new ArgumentNullException("hostNameOrAddress");
            }

            if (hostNameOrAddress.Length > 126)
            {
                throw new ArgumentOutOfRangeException(
                    "hostNameOrAddress", "The length of parameter is greater than 126 characters.");
            }

            Contract.EndContractBlock();

            var resolver = ResolverFactory.CreateResolver(DnsConfiguration);

            resolver.VerboseEvent += (sender, args) => OnVerboseEvent(args);

            var asyncCallback = ApmWrapBeginGetHostAddresses.Callback(resolver, requestCallback);

            var beginGetHostAddresses = resolver.BeginGetHostAddresses(hostNameOrAddress, asyncCallback, state);

            return ApmWrapBeginGetHostAddresses.Return(resolver, beginGetHostAddresses);
        }

        /// <summary>
        /// Begins an asynchronous request for <see cref="T:System.Net.IPHostEntry"/> information about the specified DNS host name.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.IAsyncResult"/> instance that references the asynchronous request.
        /// </returns>
        /// <param name="hostName">
        /// The DNS name of the host.
        /// </param>
        /// <param name="requestCallback">
        /// An <see cref="T:System.AsyncCallback"/> delegate that references the method to invoke when the operation is complete.
        /// </param>
        /// <param name="stateObject">
        /// A user-defined object that contains information about the operation. This object is passed to the
        ///     <paramref name="requestCallback"/>
        ///     delegate when the operation is complete.
        /// </param>
        /// <exception cref="T:System.ArgumentNullException">
        /// <paramref name="hostName"/> is null.
        /// </exception>
        /// <exception cref="T:System.Net.Sockets.SocketException">
        /// An error was encountered executing the DNS query.
        /// </exception>
        /// <PermissionSet>
        ///     <IPermission class="System.Security.Permissions.EnvironmentPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/>
        ///     <IPermission class="System.Security.Permissions.FileIOPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/>
        ///     <IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="UnmanagedCode, ControlEvidence"/>
        ///     <IPermission class="System.Net.DnsPermission, System, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/>
        /// </PermissionSet>
        [Obsolete("BeginGetHostByName is obsoleted for this type, please use BeginGetHostEntry instead. http://go.microsoft.com/fwlink/?linkid=14202")]
        [HostProtection(SecurityAction.LinkDemand, ExternalThreading = true)]
        public static IAsyncResult BeginGetHostByName(
            string hostName, AsyncCallback requestCallback, object stateObject)
        {
            if (hostName == null)
            {
                throw new ArgumentNullException("hostName");
            }

            if (requestCallback == null)
            {
                throw new ArgumentNullException("requestCallback");
            }

            Contract.EndContractBlock();

            var resolver = ResolverFactory.CreateResolver(DnsConfiguration);

            resolver.VerboseEvent += (sender, args) => OnVerboseEvent(args);

            var asyncCallback = ApmWrapBeginGetHostByName.Callback(resolver, requestCallback);

            var beginGetHostAddresses = resolver.BeginGetHostByName(hostName, asyncCallback, stateObject);

            return ApmWrapBeginGetHostByName.Return(resolver, beginGetHostAddresses);
        }

        /// <summary>
        /// Asynchronously resolves a host name or IP address to an <see cref="T:System.Net.IPHostEntry"/> instance.
        /// </summary>
        /// <param name="hostNameOrAddress">
        /// The host name or IP address to resolve.
        /// </param>
        /// <param name="requestCallback">
        /// An <see cref="T:System.AsyncCallback"/> delegate that references the method to invoke when the operation is complete.
        /// </param>
        /// <param name="stateObject">
        /// A user-defined object that contains information about the operation. This object is passed to the
        ///     <paramref name="requestCallback"/>
        ///     delegate when the operation is complete.
        /// </param>
        /// <returns>
        /// An <see cref="T:System.IAsyncResult"/> instance that references the asynchronous request.
        /// </returns>
        /// <exception cref="System.ArgumentNullException">
        /// requestCallback
        ///     or
        ///     hostNameOrAddress
        /// </exception>
        /// <exception cref="System.ArgumentOutOfRangeException">
        /// hostNameOrAddress;The length of parameter is greater than 126 characters.
        /// </exception>
        /// <exception cref="T:System.ArgumentNullException">
        /// <paramref name="hostNameOrAddress"/> is null.
        /// </exception>
        /// <exception cref="T:System.ArgumentOutOfRangeException">
        /// The length of <paramref name="hostNameOrAddress"/> is greater than 126 characters.
        /// </exception>
        /// <exception cref="T:System.Net.Sockets.SocketException">
        /// An error is encountered when resolving <paramref name="hostNameOrAddress"/>.
        /// </exception>
        /// <exception cref="T:System.ArgumentException">
        /// <paramref name="hostNameOrAddress"/> is an invalid IP address.
        /// </exception>
        [HostProtection(SecurityAction.LinkDemand, ExternalThreading = true)]
        public static IAsyncResult BeginGetHostEntry(
            string hostNameOrAddress, AsyncCallback requestCallback, object stateObject)
        {
            if (requestCallback == null)
            {
                throw new ArgumentNullException("requestCallback");
            }

            if (string.IsNullOrEmpty(hostNameOrAddress))
            {
                throw new ArgumentNullException("hostNameOrAddress");
            }

            if (hostNameOrAddress.Length > 126)
            {
                throw new ArgumentOutOfRangeException(
                    "hostNameOrAddress", "The length of parameter is greater than 126 characters.");
            }

            Contract.EndContractBlock();

            HostEntryResolveMethodType = HostEntryResolveMethodType.ByHostNameOrAddress;

            var resolver = ResolverFactory.CreateResolver(DnsConfiguration);

            resolver.VerboseEvent += (sender, args) => OnVerboseEvent(args);

            var asyncCallback = ApmWrapBeginGetHostEntryByHostNameOrAddress.Callback(resolver, requestCallback);

            var beginGetHostAddresses = resolver.BeginGetHostAddresses(hostNameOrAddress, asyncCallback, stateObject);

            return ApmWrapBeginGetHostEntryByHostNameOrAddress.Return(resolver, beginGetHostAddresses);
        }

        /// <summary>
        /// Asynchronously resolves an IP address to an <see cref="T:System.Net.IPHostEntry"/> instance.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.IAsyncResult"/> instance that references the asynchronous request.
        /// </returns>
        /// <param name="address">
        /// The IP address to resolve.
        /// </param>
        /// <param name="requestCallback">
        /// An <see cref="T:System.AsyncCallback"/> delegate that references the method to invoke when the operation is complete.
        /// </param>
        /// <param name="stateObject">
        /// A user-defined object that contains information about the operation. This object is passed to the
        ///     <paramref name="requestCallback"/>
        ///     delegate when the operation is complete.
        /// </param>
        /// <exception cref="T:System.ArgumentNullException">
        /// <paramref name="address"/> is null.
        /// </exception>
        /// <exception cref="T:System.Net.Sockets.SocketException">
        /// An error is encountered when resolving <paramref name="address"/>.
        /// </exception>
        /// <exception cref="T:System.ArgumentException">
        /// <paramref name="address"/> is an invalid IP address.
        /// </exception>
        [HostProtection(SecurityAction.LinkDemand, ExternalThreading = true)]
        public static IAsyncResult BeginGetHostEntry(
            IPAddress address, AsyncCallback requestCallback, object stateObject)
        {
            if (address == null)
            {
                throw new ArgumentNullException("address");
            }

            if (requestCallback == null)
            {
                throw new ArgumentNullException("requestCallback");
            }

            Contract.EndContractBlock();

            HostEntryResolveMethodType = HostEntryResolveMethodType.ByAddress;

            var resolver = ResolverFactory.CreateResolver(DnsConfiguration);

            resolver.VerboseEvent += (sender, args) => OnVerboseEvent(args);

            var asyncCallback = ApmWrapBeginGetHostEntryByAddress.Callback(resolver, requestCallback);

            var beginGetHostAddresses = resolver.BeginGetHostEntry(address, asyncCallback, stateObject);

            return ApmWrapBeginGetHostEntryByHostNameOrAddress.Return(resolver, beginGetHostAddresses);
        }

        /// <summary>
        /// Begins the query.
        /// </summary>
        /// <param name="name">
        /// The name.
        /// </param>
        /// <param name="queryType">
        /// Type of the query.
        /// </param>
        /// <param name="queryClassType">
        /// Type of the query class.
        /// </param>
        /// <param name="asyncCallback">
        /// The async callback.
        /// </param>
        /// <param name="state">
        /// The state.
        /// </param>
        /// <returns>
        /// The <see cref="IAsyncResult"/>.
        /// </returns>
        /// <exception cref="System.ArgumentNullException">
        /// name
        ///     or
        ///     asyncCallback is null
        /// </exception>
        [HostProtection(SecurityAction.LinkDemand, ExternalThreading = true)]
        public static IAsyncResult BeginQuery(
            string name, QType queryType, QClassType queryClassType, AsyncCallback asyncCallback, object state)
        {
            if (string.IsNullOrEmpty(name))
            {
                throw new ArgumentNullException("name");
            }

            if (asyncCallback == null)
            {
                throw new ArgumentNullException("asyncCallback");
            }

            Contract.EndContractBlock();

            var resolver = ResolverFactory.CreateResolver(DnsConfiguration);

            resolver.VerboseEvent += (sender, args) => OnVerboseEvent(args);

            var callback = ApmWrapBeginQuery.Callback(resolver, asyncCallback);

            var beginQuery = resolver.BeginQuery(name, queryType, queryClassType, callback, state);

            return ApmWrapBeginQuery.Return(resolver, beginQuery);
        }

        /// <summary>
        /// Begins an asynchronous request to resolve a DNS host name or IP address to an <see cref="T:System.Net.IPAddress"/> instance.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.IAsyncResult"/> instance that references the asynchronous request.
        /// </returns>
        /// <param name="hostName">
        /// The DNS name of the host.
        /// </param>
        /// <param name="requestCallback">
        /// An <see cref="T:System.AsyncCallback"/> delegate that references the method to invoke when the operation is complete.
        /// </param>
        /// <param name="stateObject">
        /// A user-defined object that contains information about the operation. This object is passed to the
        ///     <paramref name="requestCallback"/>
        ///     delegate when the operation is complete.
        /// </param>
        /// <exception cref="T:System.ArgumentNullException">
        /// <paramref name="hostName"/> is null.
        /// </exception>
        /// <exception cref="T:System.Net.Sockets.SocketException">
        /// The caller does not have permission to access DNS information.
        /// </exception>
        /// <PermissionSet>
        ///     <IPermission class="System.Security.Permissions.EnvironmentPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/>
        ///     <IPermission class="System.Security.Permissions.FileIOPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/>
        ///     <IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="UnmanagedCode, ControlEvidence"/>
        ///     <IPermission class="System.Net.DnsPermission, System, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/>
        /// </PermissionSet>
        [Obsolete("BeginResolve is obsoleted for this type, please use BeginGetHostEntry instead. http://go.microsoft.com/fwlink/?linkid=14202")]
        [HostProtection(SecurityAction.LinkDemand, ExternalThreading = true)]
        public static IAsyncResult BeginResolve(string hostName, AsyncCallback requestCallback, object stateObject)
        {
            if (requestCallback == null)
            {
                throw new ArgumentNullException("requestCallback");
            }

            if (string.IsNullOrEmpty(hostName))
            {
                throw new ArgumentNullException("hostName");
            }

            Contract.EndContractBlock();

            var resolver = ResolverFactory.CreateResolver(DnsConfiguration);

            resolver.VerboseEvent += (sender, args) => OnVerboseEvent(args);

            var asyncCallback = ApmWrapBeginResolve.Callback(resolver, requestCallback);

            var beginGetHostAddresses = resolver.BeginResolve(hostName, asyncCallback, stateObject);

            return ApmWrapBeginResolve.Return(resolver, beginGetHostAddresses);
        }

        /// <summary>
        /// Ends an asynchronous request for DNS information.
        /// </summary>
        /// <returns>
        /// An array of type <see cref="T:System.Net.IPAddress"/> that holds the IP addresses for the host specified by the
        ///     <paramref name="hostNameOrAddress"/>
        ///     parameter of
        ///     <see cref="M:Rsft.Net.Dns.BeginGetHostAddresses(System.String,System.AsyncCallback,System.Object)"/>
        ///     .
        /// </returns>
        /// <param name="asyncResult">
        /// An <see cref="T:System.IAsyncResult"/> instance returned by a call to the
        ///     <see cref="M:Rsft.Net.Dns.BeginGetHostAddresses(System.String,System.AsyncCallback,System.Object)"/>
        ///     method.
        /// </param>
        public static IPAddress[] EndGetHostAddresses(IAsyncResult asyncResult)
        {
            if (asyncResult == null)
            {
                throw new ArgumentNullException("asyncResult");
            }

            Contract.EndContractBlock();

            var resolver = ApmWrapBeginGetHostAddresses.Unwrap(ref asyncResult);

            if (resolver == null)
            {
                throw new InvalidOperationException("Cannot unwrap resolver.");
            }

            var addresses = resolver.EndGetHostAddresses(asyncResult);

            return addresses;
        }

        /// <summary>
        /// Ends the name of the get host by.
        /// </summary>
        /// <param name="asyncResult">
        /// The async result.
        /// </param>
        /// <returns>
        /// The <see cref="IPHostEntry"/>.
        /// </returns>
        public static IPHostEntry EndGetHostByName(IAsyncResult asyncResult)
        {
            if (asyncResult == null)
            {
                throw new ArgumentNullException("asyncResult");
            }

            Contract.EndContractBlock();

            var resolver = ApmWrapBeginGetHostByName.Unwrap(ref asyncResult);

            if (resolver == null)
            {
                throw new InvalidOperationException("Cannot unwrap resolver.");
            }

            var hostByName = resolver.EndGetHostByName(asyncResult);

            return hostByName;
        }

        /// <summary>
        /// Ends an asynchronous request for DNS information.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Net.IPHostEntry"/> instance that contains address information about the host.
        /// </returns>
        /// <param name="asyncResult">
        /// An <see cref="T:System.IAsyncResult"/> instance returned by a call to an
        ///     <see cref="Overload:Rsft.Net.Dns.BeginGetHostEntry"/>
        ///     method.
        /// </param>
        /// <exception cref="T:System.ArgumentNullException">
        /// <paramref name="asyncResult"/> is null.
        /// </exception>
        public static IPHostEntry EndGetHostEntry(IAsyncResult asyncResult)
        {
            if (asyncResult == null)
            {
                throw new ArgumentNullException("asyncResult");
            }

            Contract.EndContractBlock();

            IResolver resolver;

            if (HostEntryResolveMethodType == HostEntryResolveMethodType.ByHostNameOrAddress)
            {
                resolver = ApmWrapBeginGetHostEntryByHostNameOrAddress.Unwrap(ref asyncResult);

                if (resolver == null)
                {
                    throw new InvalidOperationException("Cannot unwrap resolver.");
                }

                return resolver.EndGetHostEntry(asyncResult);
            }

            if (HostEntryResolveMethodType == HostEntryResolveMethodType.ByAddress)
            {
                resolver = ApmWrapBeginGetHostEntryByAddress.Unwrap(ref asyncResult);

                if (resolver == null)
                {
                    throw new InvalidOperationException("Cannot unwrap resolver.");
                }

                return resolver.EndGetHostEntry(asyncResult);
            }

            throw new InvalidOperationException("HostEntryResolveMethodType not set.");
        }

        /// <summary>
        /// Ends the query.
        /// </summary>
        /// <param name="asyncResult">
        /// The async result.
        /// </param>
        /// <returns>
        /// The <see cref="Response"/>.
        /// </returns>
        /// <exception cref="System.ArgumentNullException">
        /// asyncResult is null
        /// </exception>
        public static Response EndQuery(IAsyncResult asyncResult)
        {
            if (asyncResult == null)
            {
                throw new ArgumentNullException("asyncResult");
            }

            Contract.EndContractBlock();

            var resolver = ApmWrapBeginQuery.Unwrap(ref asyncResult);

            if (resolver == null)
            {
                throw new InvalidOperationException("Cannot unwrap resolver.");
            }

            var response = resolver.EndQuery(asyncResult);

            return response;
        }

        /// <summary>
        /// Ends an asynchronous request for DNS information.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Net.IPHostEntry"/> object that contains DNS information about a host.
        /// </returns>
        /// <param name="asyncResult">
        /// An <see cref="T:System.IAsyncResult"/> instance that is returned by a call to the
        ///     <see cref="M:System.Net.Dns.BeginResolve(System.String,System.AsyncCallback,System.Object)"/>
        ///     method.
        /// </param>
        /// <exception cref="T:System.ArgumentNullException">
        /// <paramref name="asyncResult"/> is null.
        /// </exception>
        /// <PermissionSet>
        ///     <IPermission class="System.Security.Permissions.EnvironmentPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/>
        ///     <IPermission class="System.Security.Permissions.FileIOPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/>
        ///     <IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="UnmanagedCode, ControlEvidence"/>
        /// </PermissionSet>
        [Obsolete("EndResolve is obsoleted for this type, please use EndGetHostEntry instead. http://go.microsoft.com/fwlink/?linkid=14202")]
        public static IPHostEntry EndResolve(IAsyncResult asyncResult)
        {
            if (asyncResult == null)
            {
                throw new ArgumentNullException("asyncResult");
            }

            Contract.EndContractBlock();
            
            var resolver = ApmWrapBeginResolve.Unwrap(ref asyncResult);

            if (resolver == null)
            {
                throw new InvalidOperationException("Cannot unwrap resolver.");
            }

            var entry = resolver.EndResolve(asyncResult);

            return entry;
        }

        /// <summary>
        /// Gets the ARPA from IP.
        /// </summary>
        /// <param name="address">The address.</param>
        /// <returns>The ARPA formatted query for rDNS lookups</returns>
        /// <remarks>
        /// Use this method to convert IP addresses to a format suitable for PTR lookup queries.
        /// </remarks>
        /// <exception cref="System.ArgumentNullException">address is null</exception>
        public static string GetArpaFromIp(IPAddress address)
        {
            if (address == null)
            {
                throw new ArgumentNullException("address");
            }

            Contract.EndContractBlock();

            var resolver = ResolverFactory.CreateResolver(DnsConfiguration);

            resolver.VerboseEvent += (sender, args) => OnVerboseEvent(args);

            return resolver.GetArpaFromIp(address);
        }

        /// <summary>
        /// Returns the Internet Protocol (IP) addresses for the specified host.
        /// </summary>
        /// <returns>
        /// An array of type <see cref="T:System.Net.IPAddress"/> that holds the IP addresses for the host that is specified by the
        ///     <paramref name="hostNameOrAddress"/>
        ///     parameter.
        /// </returns>
        /// <param name="hostNameOrAddress">
        /// The host name or IP address to resolve.
        /// </param>
        /// <exception cref="T:System.ArgumentNullException">
        /// <paramref name="hostNameOrAddress"/> is null.
        /// </exception>
        /// <exception cref="T:System.ArgumentOutOfRangeException">
        /// The length of <paramref name="hostNameOrAddress"/> is greater than 126 characters.
        /// </exception>
        /// <exception cref="T:System.Net.Sockets.SocketException">
        /// An error is encountered when resolving <paramref name="hostNameOrAddress"/>.
        /// </exception>
        /// <exception cref="T:System.ArgumentException">
        /// <paramref name="hostNameOrAddress"/> is an invalid IP address.
        /// </exception>
        public static IPAddress[] GetHostAddresses(string hostNameOrAddress)
        {
            if (hostNameOrAddress == null)
            {
                throw new ArgumentNullException("hostNameOrAddress");
            }

            if (hostNameOrAddress.Length > 126)
            {
                throw new ArgumentOutOfRangeException(
                    "hostNameOrAddress", "The length of parameter is greater than 126 characters.");
            }

            Contract.EndContractBlock();

            var resolver = ResolverFactory.CreateResolver(DnsConfiguration);

            resolver.VerboseEvent += (sender, args) => OnVerboseEvent(args);

            return resolver.GetHostAddresses(hostNameOrAddress);
        }

        /// <summary>
        /// Creates an <see cref="T:System.Net.IPHostEntry"/> instance from an IP address.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Net.IPHostEntry"/> instance.
        /// </returns>
        /// <param name="address">
        /// An IP address.
        /// </param>
        /// <exception cref="T:System.ArgumentNullException">
        /// <paramref name="address"/> is null.
        /// </exception>
        /// <exception cref="T:System.Net.Sockets.SocketException">
        /// An error is encountered when resolving <paramref name="address"/>.
        /// </exception>
        /// <exception cref="T:System.FormatException">
        /// <paramref name="address"/> is not a valid IP address.
        /// </exception>
        /// <PermissionSet>
        ///     <IPermission class="System.Security.Permissions.EnvironmentPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/>
        ///     <IPermission class="System.Security.Permissions.FileIOPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/>
        ///     <IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="UnmanagedCode, ControlEvidence"/>
        ///     <IPermission class="System.Net.DnsPermission, System, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/>
        /// </PermissionSet>
        [Obsolete("GetHostByAddress is obsoleted for this type, please use GetHostEntry instead. http://go.microsoft.com/fwlink/?linkid=14202")]
        public static IPHostEntry GetHostByAddress(string address)
        {
            if (string.IsNullOrEmpty(address))
            {
                throw new ArgumentNullException("address");
            }

            Contract.EndContractBlock();

            /*Throw expected FormatException if invalid format*/
            IPAddress.Parse(address);

            var resolver = ResolverFactory.CreateResolver(DnsConfiguration);

            resolver.VerboseEvent += (sender, args) => OnVerboseEvent(args);

            return resolver.GetHostByAddress(address);
        }

        /// <summary>
        /// Creates an <see cref="T:System.Net.IPHostEntry"/> instance from the specified <see cref="T:System.Net.IPAddress"/>.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Net.IPHostEntry"/>.
        /// </returns>
        /// <param name="address">
        /// An <see cref="T:System.Net.IPAddress"/>.
        /// </param>
        /// <exception cref="T:System.ArgumentNullException">
        /// <paramref name="address"/> is null.
        /// </exception>
        /// <exception cref="T:System.Net.Sockets.SocketException">
        /// An error is encountered when resolving <paramref name="address"/>.
        /// </exception>
        /// <PermissionSet>
        ///     <IPermission class="System.Security.Permissions.EnvironmentPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/>
        ///     <IPermission class="System.Security.Permissions.FileIOPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/>
        ///     <IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="UnmanagedCode, ControlEvidence"/>
        ///     <IPermission class="System.Net.DnsPermission, System, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/>
        /// </PermissionSet>
        [Obsolete("GetHostByAddress is obsoleted for this type, please use GetHostEntry instead. http://go.microsoft.com/fwlink/?linkid=14202")]
        public static IPHostEntry GetHostByAddress(IPAddress address)
        {
            if (address == null)
            {
                throw new ArgumentNullException("address");
            }

            Contract.EndContractBlock();

            var resolver = ResolverFactory.CreateResolver(DnsConfiguration);

            resolver.VerboseEvent += (sender, args) => OnVerboseEvent(args);

            return resolver.GetHostEntry(address);
        }

        /// <summary>
        /// Gets the DNS information for the specified DNS host name.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Net.IPHostEntry"/> object that contains host information for the address specified in
        ///     <paramref name="hostName"/>
        ///     .
        /// </returns>
        /// <param name="hostName">
        /// The DNS name of the host.
        /// </param>
        /// <exception cref="T:System.ArgumentNullException">
        /// <paramref name="hostName"/> is null.
        /// </exception>
        /// <exception cref="T:System.ArgumentOutOfRangeException">
        /// The length of <paramref name="hostName"/> is greater than 126 characters.
        /// </exception>
        /// <exception cref="T:System.Net.Sockets.SocketException">
        /// An error is encountered when resolving <paramref name="hostName"/>.
        /// </exception>
        /// <PermissionSet>
        ///     <IPermission class="System.Security.Permissions.EnvironmentPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/>
        ///     <IPermission class="System.Security.Permissions.FileIOPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/>
        ///     <IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="UnmanagedCode, ControlEvidence"/>
        ///     <IPermission class="System.Net.DnsPermission, System, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/>
        /// </PermissionSet>
        [Obsolete("GetHostByName is obsoleted for this type, please use GetHostEntry instead. http://go.microsoft.com/fwlink/?linkid=14202")]
        public static IPHostEntry GetHostByName(string hostName)
        {
            if (string.IsNullOrEmpty(hostName))
            {
                throw new ArgumentNullException("hostName");
            }

            if (hostName.Length > 126)
            {
                throw new ArgumentOutOfRangeException(
                    "hostName", "The length of parameter is greater than 126 characters.");
            }

            Contract.EndContractBlock();

            var resolver = ResolverFactory.CreateResolver(DnsConfiguration);

            resolver.VerboseEvent += (sender, args) => OnVerboseEvent(args);

            return resolver.GetHostByName(hostName);
        }

        /// <summary>
        /// Resolves a host name or IP address to an <see cref="T:System.Net.IPHostEntry"/> instance.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Net.IPHostEntry"/> instance that contains address information about the host specified in
        ///     <paramref name="hostNameOrAddress"/>
        ///     .
        /// </returns>
        /// <param name="hostNameOrAddress">
        /// The host name or IP address to resolve.
        /// </param>
        /// <exception cref="T:System.ArgumentNullException">
        /// The <paramref name="hostNameOrAddress"/> parameter is null.
        /// </exception>
        /// <exception cref="T:System.ArgumentOutOfRangeException">
        /// The length of <paramref name="hostNameOrAddress"/> parameter is greater than 126 characters.
        /// </exception>
        /// <exception cref="T:System.Net.Sockets.SocketException">
        /// An error was encountered when resolving the <paramref name="hostNameOrAddress"/> parameter.
        /// </exception>
        /// <exception cref="T:System.ArgumentException">
        /// The <paramref name="hostNameOrAddress"/> parameter is an invalid IP address.
        /// </exception>
        public static IPHostEntry GetHostEntry(string hostNameOrAddress)
        {
            if (string.IsNullOrEmpty(hostNameOrAddress))
            {
                throw new ArgumentNullException("hostNameOrAddress");
            }

            if (hostNameOrAddress.Length > 126)
            {
                throw new ArgumentOutOfRangeException(
                    "hostNameOrAddress", "The length of parameter is greater than 126 characters.");
            }

            Contract.EndContractBlock();

            var resolver = ResolverFactory.CreateResolver(DnsConfiguration);

            resolver.VerboseEvent += (sender, args) => OnVerboseEvent(args);

            return resolver.GetHostEntry(hostNameOrAddress);
        }

        /// <summary>
        /// Resolves an IP address to an <see cref="T:System.Net.IPHostEntry"/> instance.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Net.IPHostEntry"/> instance that contains address information about the host specified in
        ///     <paramref name="address"/>
        ///     .
        /// </returns>
        /// <param name="address">
        /// An IP address.
        /// </param>
        /// <exception cref="T:System.ArgumentNullException">
        /// <paramref name="address"/> is null.
        /// </exception>
        /// <exception cref="T:System.Net.Sockets.SocketException">
        /// An error is encountered when resolving <paramref name="address"/>.
        /// </exception>
        /// <exception cref="T:System.ArgumentException">
        /// <paramref name="address"/> is an invalid IP address.
        /// </exception>
        public static IPHostEntry GetHostEntry(IPAddress address)
        {
            if (address == null)
            {
                throw new ArgumentNullException("address");
            }

            Contract.EndContractBlock();

            var resolver = ResolverFactory.CreateResolver(DnsConfiguration);

            resolver.VerboseEvent += (sender, args) => OnVerboseEvent(args);

            return resolver.GetHostEntry(address);
        }

        /// <summary>
        ///     Gets the host name of the local computer.
        /// </summary>
        /// <returns>
        ///     A string that contains the DNS host name of the local computer.
        /// </returns>
        /// <exception cref="T:System.Net.Sockets.SocketException">
        ///     An error is encountered when resolving the local host name.
        /// </exception>
        /// <PermissionSet>
        ///     <IPermission
        ///         class="System.Net.DnsPermission, System, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
        ///         version="1" Unrestricted="true" />
        /// </PermissionSet>
        public static string GetHostName()
        {
            var resolver = ResolverFactory.CreateResolver(DnsConfiguration);

            resolver.VerboseEvent += (sender, args) => OnVerboseEvent(args);

            return resolver.GetHostName();
        }

        /// <summary>
        /// Queries the specified name.
        /// </summary>
        /// <param name="name">
        /// The name.
        /// </param>
        /// <param name="queryType">
        /// Type of the query.
        /// </param>
        /// <param name="queryClassType">
        /// Type of the query class.
        /// </param>
        /// <returns>
        /// The <see cref="Response"/>.
        /// </returns>
        /// <exception cref="System.ArgumentNullException">
        /// name is null.
        /// </exception>
        public static Response Query(string name, QType queryType, QClassType queryClassType = QClassType.IN)
        {
            if (string.IsNullOrEmpty(name))
            {
                throw new ArgumentNullException("name");
            }

            Contract.EndContractBlock();

            var resolver = ResolverFactory.CreateResolver(DnsConfiguration);

            resolver.VerboseEvent += (sender, args) => OnVerboseEvent(args);

            return resolver.Query(name, queryType, queryClassType);
        }

        /// <summary>
        /// Resolves a DNS host name or IP address to an <see cref="T:System.Net.IPHostEntry"/> instance.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Net.IPHostEntry"/> instance that contains address information about the host specified in
        ///     <paramref name="hostName"/>
        ///     .
        /// </returns>
        /// <param name="hostName">
        /// A DNS-style host name or IP address.
        /// </param>
        /// <exception cref="T:System.ArgumentNullException">
        /// <paramref name="hostName"/> is null.
        /// </exception>
        /// <exception cref="T:System.ArgumentOutOfRangeException">
        /// The length of <paramref name="hostName"/> is greater than 126 characters.
        /// </exception>
        /// <exception cref="T:System.Net.Sockets.SocketException">
        /// An error is encountered when resolving <paramref name="hostName"/>.
        /// </exception>
        /// <PermissionSet>
        ///     <IPermission class="System.Security.Permissions.EnvironmentPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/>
        ///     <IPermission class="System.Security.Permissions.FileIOPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/>
        ///     <IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="UnmanagedCode, ControlEvidence"/>
        ///     <IPermission class="System.Net.DnsPermission, System, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/>
        /// </PermissionSet>
        [Obsolete("Resolve is obsoleted for this type, please use GetHostEntry instead. http://go.microsoft.com/fwlink/?linkid=14202")]
        public static IPHostEntry Resolve(string hostName)
        {
            if (string.IsNullOrEmpty(hostName))
            {
                throw new ArgumentNullException("hostName");
            }

            if (hostName.Length > 126)
            {
                throw new ArgumentOutOfRangeException(
                    "hostName", "The length of parameter is greater than 126 characters.");
            }

            Contract.EndContractBlock();

            var resolver = ResolverFactory.CreateResolver(DnsConfiguration);

            resolver.VerboseEvent += (sender, args) => OnVerboseEvent(args);

            return resolver.Resolve(hostName);
        }

        #endregion

        #region Methods

        /// <summary>
        /// Raises the <see cref="E:VerboseEvent" /> event.
        /// </summary>
        /// <param name="e">The <see cref="VerboseEventArgs"/> instance containing the event data.</param>
        private static void OnVerboseEvent(VerboseEventArgs e)
        {
            var handler = VerboseEvent;
            if (handler != null)
            {
                handler(null, e);
            }
        }

        #endregion
    }
}